{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"http://www.codeheroku.com/static/blog/images/pid14_results.png\">\n",
    "\n",
    "Wondered how Google comes up with movies that are similar to the ones you like? After reading this post you will be able to build one such recommendation system for yourself.\n",
    "\n",
    "It turns out that there are (mostly) three ways to build a recommendation engine:\n",
    "\n",
    "1. Popularity based recommendation engine\n",
    "2. Content based recommendation engine\n",
    "3. Collaborative filtering based recommendation engine\n",
    "\n",
    "Now you might be thinking “That’s interesting. But, what are the differences between these recommendation engines?”. Let me help you out with that.\n",
    "\n",
    "### Popularity based recommendation engine:\n",
    "\n",
    "Perhaps, this is the simplest kind of recommendation engine that you will come across. The trending list you see in YouTube or Netflix is based on this algorithm. It keeps a track of view counts for each movie/video and then lists movies based on views in descending order(highest view count to lowest view count). Pretty simple but, effective. Right?\n",
    "\n",
    "\n",
    "### Content based recommendation engine:\n",
    "\n",
    "This type of recommendation systems, takes in a movie that a user currently likes as input. Then it analyzes the contents (storyline, genre, cast, director etc.) of the movie to find out other movies which have similar content. Then it ranks similar movies according to their similarity scores and recommends the most relevant movies to the user.\n",
    "\n",
    "### Collaborative filtering based recommendation engine:\n",
    "\n",
    "This algorithm at first tries to find similar users based on their activities and preferences (for example, both the users watch same type of movies or movies directed by the same director). Now, between these users(say, A and B) if user A has seen a movie that user B has not seen yet, then that movie gets recommended to user B and vice-versa. In other words, the recommendations get filtered based on the collaboration between similar user’s preferences (thus, the name “Collaborative Filtering”). One typical application of this algorithm can be seen in the Amazon e-commerce platform, where you get to see the “Customers who viewed this item also viewed” and “Customers who bought this item also bought” list.\n",
    "\n",
    "<img src=\"http://www.codeheroku.com/static/blog/images/pid14_img1.png\">\n",
    "\n",
    "Look at the following picture to get a better intuition over content based and collaborative filtering based recommendation systems-\n",
    "\n",
    "<img src=\"http://www.codeheroku.com/static/blog/images/pid14_rs_diff.png\">\n",
    "\n",
    "Another type of recommendation system can be created by mixing properties of two or more types of recommendation systems. This type of recommendation systems are known as hybrid recommendation system.\n",
    "\n",
    "In this article, we are going to implement a Content based recommendation system using the scikit-learn library.\n",
    "\n",
    "### Finding the similarity\n",
    "\n",
    "We know that our recommendation engine will be content based. So, we need to find similar movies to a given movie and then recommend those similar movies to the user. The logic is pretty straightforward. Right?\n",
    "\n",
    "But, wait…. How can we find out which movies are similar to the given movie in the first place? How can we find out how much similar(or dissimilar) two movies are?\n",
    "\n",
    "Let us start with something simple and easy to understand.\n",
    "\n",
    "Suppose, you are given the following two texts:\n",
    "\n",
    "Text A: London Paris London\n",
    "\n",
    "Text B: Paris Paris London\n",
    "\n",
    "How would you find the similarity between Text A and Text B?\n",
    "\n",
    "Let’s analyze these texts….\n",
    "\n",
    "1. Text A: Contains the word “London” 2 times and the word “Paris” 1 time.\n",
    "2. Text B: Contains the word “London” 1 time and the word “Paris” 2 times.\n",
    "\n",
    "Now, what will happen if we try to represent these two texts in a 2D plane (with “London” in X axis and “Paris” in Y axis)? Let’s try to do this.\n",
    "\n",
    "It will look like this-\n",
    "\n",
    "<img src=\"http://www.codeheroku.com/static/blog/images/pid14_text_2d_repr.png\">\n",
    "\n",
    "Here, the red vector represents “Text A” and the blue vector represents “Text B”.\n",
    "\n",
    "Now we have graphically represented these two texts. So, now can we find out the similarity between these two texts?\n",
    "\n",
    "The answer is “Yes, we can”. But, exactly how?\n",
    "\n",
    "These two texts are represented as vectors. Right? So, we can say that two vectors are similar if the distance between them is small. By distance, we mean the angular distance between two vectors, which is represented by θ (theta). By thinking further from the machine learning perspective, we can understand that the value of cos θ makes more sense to us rather than the value of θ (theta) because, the cosine(or “cos”) function will map the value of θ in the first quadrant between 0 to 1 (Remember? cos 90° = 0 and cos 0° = 1 ).\n",
    "\n",
    "And from high school maths, we can remember that there is actually a formula for finding out cos θ between two vectors. See the picture below-\n",
    "\n",
    "<img src=\"http://www.codeheroku.com/static/blog/images/pid14_find_cos_theta.png\">\n",
    "\n",
    "Don’t get scared, we don’t need to implement the formula from scratch for finding out cos θ. We have our friend Scikit Learn to calculate that for us :)\n",
    "\n",
    "Let’s see how we can do that.\n",
    "\n",
    "At first, we need to have text A and B in our program:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "text = [\"London Paris London\",\"Paris Paris London\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we need to find a way to represent these texts as vectors. The `CountVectorizer()` class from `sklearn.feature_extraction.text` library can do this for us. We need to import this library before we can create a new `CountVectorizer()` object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.feature_extraction.text import CountVectorizer\n",
    "cv = CountVectorizer()\n",
    "count_matrix = cv.fit_transform(text)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`count_matrix` gives us a sparse matrix. To make it in human readable form, we need to apply `toarrray()` method over it. And before printing out this `count_matrix`, let us first print out the feature list(or, word list), which have been fed to our `CountVectorizer()` object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['london', 'paris']\n",
      "[[2 1]\n",
      " [1 2]]\n"
     ]
    }
   ],
   "source": [
    "print(cv.get_feature_names())\n",
    "print(count_matrix.toarray())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This indicates that the word ‘london’ occurs 2 times in A and 1 time in B. Similarly, the word ‘paris’ occurs 1 time in A and 2 times in B. Makes sense. Right?\n",
    "\n",
    "Now, we need to find cosine(or “cos”) similarity between these vectors to find out how similar they are from each other. We can calculate this using `cosine_similarity()` function from `sklearn.metrics.pairwise` library."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1.  0.8]\n",
      " [0.8 1. ]]\n"
     ]
    }
   ],
   "source": [
    "from sklearn.metrics.pairwise import cosine_similarity\n",
    "similarity_scores = cosine_similarity(count_matrix)\n",
    "print(similarity_scores)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What does this output indicate?\n",
    "\n",
    "We can interpret this output like this-\n",
    "\n",
    "1. Each row of the similarity matrix indicates each sentence of our input. So, row 0 = Text A and row 1 = Text B.\n",
    "2. The same thing applies for columns. To get a better understanding over this, we can say that the output given above is same as the following:\n",
    "\n",
    "<code>\n",
    "        Text A:     Text B:\n",
    "Text A: [[1.         0.8]  \n",
    "Text B: [0.8         1.]]  \n",
    "</code>\n",
    "<br>\n",
    "Interpreting this, says that Text A is similar to Text A(itself) by 100%(position [0,0]) and Text A is similar to Text B by 80%(position [0,1]). And by looking at the kind of output it is giving, we can easily say that this is always going to output a symmetric matrix. Because, if Text A is similar to Text B by 80% then, Text B is also going to be similar to Text A by 80%.\n",
    "\n",
    "\n",
    "Now we know how to find similarity between contents. So, let’s try to apply this knowledge to build a content based movie recommendation engine.\n",
    "\n",
    "### Building the recommendation engine:\n",
    "\n",
    ">The movie dataset that we are going to use in our recommendation engine can be downloaded from [Course Github Repo](https://github.com/codeheroku/Introduction-to-Machine-Learning/blob/master/Building%20a%20Movie%20Recommendation%20Engine/movie_dataset.csv).\n",
    "\n",
    "After downloading the dataset, we need to import all the required libraries and then read the csv file using `read_csv()` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "from sklearn.feature_extraction.text import CountVectorizer\n",
    "from sklearn.metrics.pairwise import cosine_similarity\n",
    "\n",
    "df = pd.read_csv(\"movie_dataset.csv\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you visualize the dataset, you will see that it has many extra info about a movie. We don’t need all of them. So, we choose keywords, cast, genres and director column to use as our feature set(the so called “content” of the movie)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "features = ['keywords','cast','genres','director']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our next task is to create a function for combining the values of these columns into a single string."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def combine_features(row):\n",
    "    return row['keywords']+\" \"+row['cast']+\" \"+row['genres']+\" \"+row['director']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we need to call this function over each row of our dataframe. But, before doing that, we need to clean and preprocess the data for our use. We will fill all the NaN values with blank string in the dataframe."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "for feature in features:\n",
    "    df[feature] = df[feature].fillna('') #filling all NaNs with blank string\n",
    "\n",
    "df[\"combined_features\"] = df.apply(combine_features,axis=1) #applying combined_features() method over each rows of dataframe and storing the combined string in \"combined_features\" column\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'culture clash future space war space colony society Sam Worthington Zoe Saldana Sigourney Weaver Stephen Lang Michelle Rodriguez Action Adventure Fantasy Science Fiction James Cameron'"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df.iloc[0].combined_features"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we have obtained the combined strings, we can now feed these strings to a CountVectorizer() object for getting the count matrix."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "cv = CountVectorizer() #creating new CountVectorizer() object\n",
    "count_matrix = cv.fit_transform(df[\"combined_features\"]) #feeding combined strings(movie contents) to CountVectorizer() object"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At this point, 60% work is done. Now, we need to obtain the cosine similarity matrix from the count matrix."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "cosine_sim = cosine_similarity(count_matrix)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we will define two helper functions to get movie title from movie index and vice-versa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_title_from_index(index):\n",
    "    return df[df.index == index][\"title\"].values[0]\n",
    "def get_index_from_title(title):\n",
    "    return df[df.title == title][\"index\"].values[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our next step is to get the title of the movie that the user currently likes. Then we will find the index of that movie. After that, we will access the row corresponding to this movie in the similarity matrix. Thus, we will get the similarity scores of all other movies from the current movie. Then we will enumerate through all the similarity scores of that movie to make a tuple of movie index and similarity score. This will convert a row of similarity scores like this- `[1 0.5 0.2 0.9]` to this- `[(0, 1) (1, 0.5) (2, 0.2) (3, 0.9)]` . Here, each item is in this form- (movie index, similarity score)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "movie_user_likes = \"Avatar\"\n",
    "movie_index = get_index_from_title(movie_user_likes)\n",
    "similar_movies = list(enumerate(cosine_sim[movie_index])) #accessing the row corresponding to given movie to find all the similarity scores for that movie and then enumerating over it"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Now comes the most vital point. We will sort the list `similar_movies` according to similarity scores in descending order. Since the most similar movie to a given movie will be itself, we will discard the first element after sorting the movies."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "sorted_similar_movies = sorted(similar_movies,key=lambda x:x[1],reverse=True)[1:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we will run a loop to print first 5 entries from `sorted_similar_movies` list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Top 5 similar movies to Avatar are:\n",
      "\n",
      "Guardians of the Galaxy\n",
      "Aliens\n",
      "Star Wars: Clone Wars: Volume 1\n",
      "Star Trek Into Darkness\n",
      "Star Trek Beyond\n",
      "Alien\n"
     ]
    }
   ],
   "source": [
    "i=0\n",
    "print(\"Top 5 similar movies to \"+movie_user_likes+\" are:\\n\")\n",
    "for element in sorted_similar_movies:\n",
    "    print(get_title_from_index(element[0]))\n",
    "    i=i+1\n",
    "    if i>5:\n",
    "        break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we are done here!\n",
    "\n",
    "> You can download the Python script and associated datasets from [Course Github Repo](https://github.com/codeheroku/Introduction-to-Machine-Learning/tree/master/Building%20a%20Movie%20Recommendation%20Engine).\n",
    "\n",
    "After seeing the output, I went one step further to compare it to other recommendation engines.\n",
    "\n",
    "So, I searched Google for similar movies to “Avatar” and here is what I got-\n",
    "\n",
    "<img src=\"http://www.codeheroku.com/static/blog/images/pid14_results.png\">\n",
    "\n",
    "See the output? Our simple movie recommendation engine works pretty good. Right? It’s good as a basic level implementation but, it can be further improved with many other factors. Try to optimize this recommendation engine yourself and let us know your story at hello@codeheroku.com.\n",
    "\n",
    ">If this article was helpful to you, check out our [Introduction to Machine Learning](http://www.codeheroku.com/course?course_id=1) Course at [Code Heroku](http://www.codeheroku.com/) for a complete guide to Machine Learning.\n",
    "\n",
    "<br><br>\n",
    "<p align=\"center\"><a href=\"http://www.codeheroku.com/\">\n",
    " <img src=\"http://www.codeheroku.com/static/images/logo5.png\"></a>\n",
    "</p>\n",
    "\n",
    "<br>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
